require 'chess.rb'
require "position"

class Chess_Board
	attr_reader :board, :history, :taken, :turn, :last_2pawn_move_x, :last_2pawn_move_y

	PIECE_XLATE_TABLE = {
		"k" => King,
		"q" => Queen,
		"r" => Rook,
		"b" => Bishop,
		"n" => Knight,
		"p" => Pawn
	}

	COLOR_XLATE_TABLE = {
		"w" => "white",
		"b" => "black",
		"white" => "w",
		"black" => "b"
	}

	OPPOSITE_COLOR_XLATE_TABLE = {
		"w" => "b",
		"b" => "w",
		"white" => "black",
		"black" => "white"
	}
	
	def color_xlate(color)
		COLOR_XLATE_TABLE[color]
	end

	def opposite_color(color)
		OPPOSITE_COLOR_XLATE_TABLE[color]
	end
	
	def xy_xlate(x, y)
		#puts ("x = #{x.inspect}, y = #{y.inspect}")
		"#{(x+?a).chr}#{y+1}"
	end
	
	def piece_xlate(board, x, y, position_piece_and_color)
		position_piece_and_color = " " if !position_piece_and_color || position_piece_and_color == ""
		if position_piece_and_color == " " then
			nil
		else
			#puts "position_piece_and_color = '#{position_piece_and_color}'"
		 	color, position_piece = /(\w)(\w)/.match(position_piece_and_color).captures
		 	color = color_xlate(color)
		 	$TRACE.debug 5, "at (#{x},#{y}), color = #{color}, position_piece = #{position_piece}"
		 	PIECE_XLATE_TABLE[position_piece].new(board, x, y, color)
		 end
   end

   def position_xlate(position)
   	file, row = /(\w)(\d)/.match(position).captures
   	$TRACE.debug 9, "position_xlate: position = #{position}, file = '#{file}', row = '#{row}'"
   	x = file[0] - ?a
   	y = row.to_i - 1

   	[x, y]
   end

	class NoPieceToMoveError < Exception
		attr_reader :position
		def initialize(position)
			@position = position
			super("no piece at coordinates #{position}")
		end
	end

	class WrongPieceForTurnError < Exception
		attr_reader :turn
		def initialize(turn)
			@turn = turn
			super("its #{turn}'s turn")
		end
	end

	class MoveOutOfBoundsError < Exception
		attr_reader :position
		def initialize(position)
			@position = position
			super("coordinates #{position} are out of bounds")
		end
	end

	class NewSquareOccupiedBySelf < Exception
		attr_reader :position
		def initialize(position)
			@position = position
			super("coordinates #{position} are occupied by mover's piece")
		end
	end

	class CastleErrorKingHasMoved < Exception
		attr_reader :position
		def initialize(board, x, y)
			@position = board.xy_xlate(x, y)
			super("Invalid castle, the king at #{@position} has moved")
		end
	end

	class CastleErrorRookHasMoved < Exception
		attr_reader :position
		def initialize(board, x, y)
			@position = board.xy_xlate(x, y)
			super("Invalid castle, the rook at #{@position} has moved")
		end
	end

	class IllegalKingMoveError < Exception
		attr_reader :dx, :dy
		def initialize(dx, dy)
			@dx, @dy = dx, dy
			super("Illegal King move, not 1,1 but #{dx}, #{dy}")
		end
	end

	class MoveIntoCheckError < Exception
		attr_reader :position
		def initialize(board, x, y)
			@position = board.xy_xlate(x, y)
			super("The King can't move into check at position #{@position}")
		end
	end
	
	class InterveningPieceError < Exception
		attr_reader :position
		def initialize(board, x,y)
			@position = board.xy_xlate(x,y)
			super("a piece at #{@position} is in the way")
		end
	end

	class NonRowColumnMove < Exception
		attr_reader :dx, :dy
		def initialize(dx, dy)
			@dx, @dy = dx, dy
			super("move is not along a row or column (delta-x,delta-y) = (#{dx}, #{dy})")
		end
	end
	
	class NonDiagonalMoveError < Exception
		attr_reader :dx, :dy
		def initialize(dx, dy)
			@dx, @dy = dx, dy
			super("move is not diagonal (delta-x, delta-y) = (#{dx}, #{dy})")
		end
	end

	class IllegalKnightMoveError < Exception
		attr_reader :dx, :dy
		def initialize(dx, dy)
			@dx, @dy = dx, dy
			super("move is an L shaped move (#{dx}, #{dy})")
		end
	end

	class IllegalPawnMoveDistanceError < Exception
		attr_reader :position, :dist
		def initialize(board, x, y, dist)
			@position = board.xy_xlate(x,y)
			@dist = dist
		end
	end

	class IllegalPawnMoveBackwardError < Exception
		def initialize
			super("illegal backward move")
		end
	end

	class IllegalPawnMoveDistanceError < Exception
		attr_reader :position, :dist
		
		def initialize(board, x, y, dist)
			@position = board.xy_xlate(x, y)
			@dist = dist
			super("illegal pawn move, distance error #{@position} distance of #{dist}")
		end
	end

	class IllegalPawnMoveEnPassantError < Exception
		attr_reader :start_position, :end_position
		def initialize(board, x, y, x_dest, y_dest)
			@start_position = board.xy_xlate(x, y)
			@end_position = board.xy_xlate(x_dest, y_dest)
			super("illegal pawn move, en passant error from #{@start_position}-#{@end_position}")
		end
	end

	class IllegalPawnMoveSquareOccupiedError < Exception
		attr_reader :position
		def initialize(board, x, y)
			@position = board.xy_xlate(x, y)
			super("illegal pawn move, square occupied #{@position}")
		end
	end

	class IllegalPawnMoveNoTargetError < Exception
		attr_reader :position
		def initialize(board, x, y)
			@position = board.xy_xlate(x, y)
			super("illegal pawn move, no target to attack at position #{@position}")
		end
	end

   class IllegalPawnLongMovePieceInterveningError < Exception
   	attr_reader :position
   	def initialize(board, x, y)
			@position = board.xy_xlate(x, y)
			super("illegal long pawn move, piece intervening")
		end
   end

	class MoveIntoCheckError < Exception
		attr_reader :position
		def initialize(board, x, y)
			@position = board.xy_xlate(x, y)
			super("the king can't move into check at position #{@position}")
		end
	end
	
	def initialize( position )
		@board = Array.new( 8 ) { Array.new( 8, nil ) }
		("a".."h").each do |file|
			(1..8).each do |row|
				x = file[0]- ?a
				y = row-1 
				@board [x][y] = piece_xlate(self, x, y, position.send("#{file}#{row}"))
				if position.last_2pawn_move_coord then
					@last_2pawn_move_x, @last_2pawn_move_y = position_xlate(position.last_2pawn_move_coord)
				end
			end
		end
		@turn = color_xlate(position.turn)
		@position = position
	end

	def square_attacked_by(attack_x, attack_y, color)
		attacking_pieces = []
		(0..7).each do |x|
			(0..7).each do |y|
				piece = board[x][y]
				$TRACE.debug 9, "checking piece #{piece.class} of color '#{piece.color}' <=> '#{color}' at #{x},#{y} to #{attack_x},#{attack_y}" if piece
				begin
					if piece && piece.color == color then
						piece.attack(attack_x,attack_y)
						$TRACE.debug 5, "piece #{piece.class} at #{x},#{y} can attack"
						attacking_pieces.push(piece)
					end
				rescue Exception => e
					$TRACE.debug 9, "can't attack because, #{e.message}"
					# ignore these move exceptions
				end
			end
		end
		attacking_pieces
	end
	
	def move(move)
		$TRACE.debug 5, "moving from #{move.starting_position_coordinates} to #{move.ending_position_coordinates}"
		move!(*(position_xlate(move.starting_position_coordinates) + position_xlate(move.ending_position_coordinates)))
	end

	def move!( xi, yi, x, y )
		$TRACE.debug 5, "moving #{xi},#{yi} to #{x},#{y}"
		
		piece = self.board[xi][yi]

		# make sure we are moving a piece
		raise NoPieceToMoveError.new(xy_xlate(xi,yi)) if piece.nil?
		
		# Make sure we are taking turns!
		raise WrongPieceForTurnError.new(@turn) if piece.color != @turn
		
		# False if destination is out of bounds
		raise MoveOutOfBoundsError.new(xy_xlate(x, y)) if x < 0 || x > 7 || y < 0 || y > 7
		# False if destination space occupied by same color
		if !self.board[x][y].nil? 
			raise NewSquareOccupiedBySelf.new(xy_xlate(x,y)) if self.board[x][y].color == piece.color
		end
		
		# Check to see if this is a "castling" move
		if piece.class == King && (x == 6 || x == 2) && (xi-x).abs > 1
			castle!( xi, yi, x, y )
		else
			# Check to make sure it is a valid move for this piece type
			$TRACE.debug 5, "before move"
			piece.move( x, y )
			$TRACE.debug 5, "after move"
            
			# Everything okay, so now we must set new piece state
			death = false
			if !self.board[x][y].nil?  # if occupied by enemy, that piece is now dead
				target_piece = self.board[x][y]
				#target_piece.dead = true
				#@taken.push target_piece
				#target_piece.x, target_piece.y = -1, -1
				death = true
			elsif piece.class == Pawn then
				$TRACE.debug 5, "moving a pawn, #{piece.inspect}"
				if piece.ep_kill then # checks for en-passant kill
					$TRACE.debug 5, "en passant kill"
					piece.ep_kill = false
					if piece.color == "white" 
						#target_piece = self.board[x][y-1]
						self.board[x][y-1] = nil
					else
						#target_piece = self.board[x][y+1]
						self.board[x][y+1] = nil
					end
					#target_piece.dead = true
					#@taken.push target_piece
					#target_piece.x, target_piece.y = -1, -1
					death = true
				end
			end
        
			self.board[x][y] = piece
			self.board[xi][yi] = nil
			piece.x = x
			piece.y = y
			temp = [ "a", "b", "c", "d", "e", "f", "g", "h" ]
			#if death
			#	hist = "#{temp[xi]}#{yi+1}x#{temp[x]}#{y+1}"
			#else
			#	hist = "#{temp[xi]}#{yi+1}..#{temp[x]}#{y+1}"
			#end
			#@history.push hist
			
			# Handle pawn promotion.  Will just promote to queen for now.
			if piece.class == Pawn && (piece.y == 7 || piece.y == 0)
				self.board[x][y] = Queen.new( self, x, y, piece.color )
			end
		end
		#vwrite( )
		@turn = (@turn == "white") ? "black" : "white"

		position = Position.new
		(0..7).each do |x|
			(0..7).each do |y|
				row_col = xy_xlate(x, y) + "="
				$TRACE.debug 9, "[#{x}, #{y}] = #{@board[x][y].class}, row_col = #{row_col}" if @board[x][y]
				if @board[x][y] then
					position.send(row_col, @board[x][y].as_mnemonic)
				else
					position.send(row_col, " ")
				end
			end
		end
		[:game, :rook_a_moved, :rook_h_moved, :king_moved].each do |sym|
			position.send("#{sym}=", @position.send(sym))
		end
		position.turn = color_xlate(@turn)
		position.save

		position
	end
    
	def castle!( xi, yi, xf, yf )
		king = @board[xi][yi]
		if xi < xf # Queenside
			rook = @board[7][yf]
		else
			rook = @board[0][yf]
		end
        
		# Check to make sure neither piece has moved
		raise Chess_Board::CastleErrorKingHasMoved.new(self, xi, yi) if king.moved
		raise Chess_Board::CastleErrorRookHasMoved.new(self, rook.x, rook.y) if rook.moved
        
		# Check to make sure there are no intervening pieces
		if xi > xf # Queenside
			raise Chess_Board::InterveningPieceError.new(self, xi-1, yf) unless @board[xi-1][yf].nil?
			raise Chess_Board::InterveningPieceError.new(self, xi-2, yf) unless @board[xi-2][yf].nil?
			raise Chess_Board::InterveningPieceError.new(self, xi-3, yf) unless @board[xi-3][yf].nil?
			#hist = "O-O-O"
		else # Kingside
			raise Chess_Board::InterveningPieceError.new(self, xi+1, yf) unless @board[xi+1][yf].nil?
			raise Chess_Board::InterveningPieceError.new(self, xi+2, yf) unless @board[xi+2][yf].nil?
			#hist = "O-O"
		end
        
		# If we made it this far, caslting is a go
		@board[xf][yf] = king
		king.x, king.y = xf, yf
		@board[xi][yi] = nil
		if xi > xf # Queenside
			@board[3][yf] = rook
			rook.x, rook.y = 3, yf
			@board[0][yf] = nil
		else
			@board[5][yf] = rook
			rook.x, rook.y = 5, yf
			@board[7][yf] = nil
		end
		king.moved=true
		rook.moved=true
		#@history.push hist
		return true
	end
    
	def draw?( )
		# Check for Draw
		w1, w2, w3 = @history[-1], @history[-3],  @history[-5]
		w4, w5, w6 = @history[-7], @history[-9],  @history[-11]
		b1, b2, b3 = @history[-2], @history[-4],  @history[-6]
		b4, b5, b6 = @history[-8], @history[-10], @history[-12]
		w1 == w3 && w3 == w5 && w2 == w4 && w4 == w6 && b1 == b3 && b3 == b5 && b2 == b4 && b4 == b6 ? true : false
	end
end
